In your final repo, there should be an R markdown file that organizes all computational steps for evaluating your proposed Facial Expression Recognition framework.
This file is currently a template for running evaluation experiments. You should update it according to your codes but following precisely the same structure.
rr if(!require()){ install.packages() BiocManager::install() } if(!require(.matlab)){ install.packages(.matlab) } if(!require()){ install.packages() } if(!require()){ install.packages() } if(!require()){ install.packages() } if(!require(2)){ install.packages(2) } if(!require()){ install.packages() } if(!require()){ install.packages() } if(!require()){ install.packages() } if(!require(1071)){ install.packages(1071) } if(!require()){ install.packages() } if(!require()){ install.packages() } library(R.matlab) library(readxl) library(dplyr) library(EBImage) library(ggplot2) library(caret) library(glmnet) library(WeightedROC) library(e1071) library(xgboost) library(randomForest)
Step 0 set work directories
set.seed(2020)
# setwd("~/Project3-FacialEmotionRecognition/doc")
# here replace it with your own path or manually set it in RStudio to where this rmd file is located.
# use relative path for reproducibility
Provide directories for training images. Training images and Training fiducial points will be in different subfolders.
rr train_dir <- ../data/train_set/ # This will be modified for different data sets. train_image_dir <- paste(train_dir, /, sep=\) train_pt_dir <- paste(train_dir, /, sep=\) train_label_path <- paste(train_dir, .csv, sep=\)
Step 1: set up controls for evaluation experiments.
In this chunk, we have a set of controls for the evaluation experiments.
- (T/F) cross-validation on the training set
- (T/F) reweighting the samples for training set
- (number) K, the number of CV folds
- (T/F) process features for training set
- (T/F) run evaluation on an independent test set
- (T/F) process features for test set
rr run.cv <- FALSE # run cross-validation on the training set run.cv.baseline <- FALSE # run cross-validation on the gbm baseline sample.reweight <- TRUE # run sample reweighting in model training K <- 5 # number of CV folds run.feature.train <- TRUE # process features for training set run.test <- TRUE # run evaluation on an independent test set run.feature.test <- TRUE # process features for test set run.cv.svm <- FALSE run.test.svm <- TRUE
Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications. In this Starter Code, we tune parameter lambda (the amount of shrinkage) for logistic regression with LASSO penalty.
rr lmbd = c(1e-3, 5e-3, 1e-2, 5e-2, 1e-1) model_labels = paste(Penalty with lambda =, lmbd) #gbm parameters tuning: n.trees = c(10,50,100,200) shrinkage = c(0.01,0.05,0.1,0.15) #svm parameters tuning: cost = c(0.0000001,0.000001,0.00001,0.0001,0.001,0.01,1) model_labels_svm = paste(with cost =, cost) #xgboost parameters tuning params <- list(booster = , objective = :logistic, eta=0.3, gamma=0, max_depth=6, min_child_weight=1, subsample=1, colsample_bytree=1) #random forest parameters tuning ntrees <- 128 #according to a paper by Thais Mayumi Oshiro, Pedro Santoro Perez, and Jos ́e Augusto Baranauska, # the AUC gain for increasing number of trees is minimal after 128, # after observing a multitude of their datasets
Step 2: import data and train-test split
rr #train-test split info <- read.csv(train_label_path) n <- nrow(info) n_train <- round(n*(4/5), 0) train_idx <- sample(info\(Index, n_train, replace = F) test_idx <- setdiff(info\)Index, train_idx)
If you choose to extract features from images, such as using Gabor filter, R memory will exhaust all images are read together. The solution is to repeat reading a smaller batch(e.g 100) and process them.
rr n_files <- length(list.files(train_image_dir)) image_list <- list() for(i in 1:100){ image_list[[i]] <- readImage(paste0(train_image_dir, sprintf(%04d, i), .jpg)) }
Fiducial points are stored in matlab format. In this step, we read them and store them in a list.
rr #function to read fiducial points #input: index #output: matrix of fiducial points corresponding to the index readMat.matrix <- function(index){ return(round(readMat(paste0(train_pt_dir, sprintf(%04d, index), .mat))[[1]],0)) } #load fiducial points fiducial_pt_list <- lapply(1:n_files, readMat.matrix) save(fiducial_pt_list, file=../output/fiducial_pt_list.RData)
Step 3: construct features and responses
The follow plots show how pairwise distance between fiducial points can work as feature for facial emotion recognition.
- In the first column, 78 fiducials points of each emotion are marked in order.
- In the second column distributions of vertical distance between right pupil(1) and right brow peak(21) are shown in histograms. For example, the distance of an angry face tends to be shorter than that of a surprised face.
The third column is the distributions of vertical distances between right mouth corner(50) and the midpoint of the upper lip(52). For example, the distance of an happy face tends to be shorter than that of a sad face.
feature.R should be the wrapper for all your feature engineering functions and options. The function feature( ) should have options that correspond to different scenarios for your project and produces an R object that contains features and responses that are required by all the models you are going to evaluate later.
feature.R
- Input: list of images or fiducial point
- Output: an RData file that contains extracted features and corresponding responses
rr source(../lib/feature.R) tm_feature_train <- NA if(run.feature.train){ tm_feature_train <- system.time(dat_train <- feature(fiducial_pt_list, train_idx)) save(dat_train, file=../output/feature_train.RData) }else{ load(file=../output/feature_train.RData) } tm_feature_test <- NA if(run.feature.test){ tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx)) save(dat_test, file=../output/feature_test.RData) }else{ load(file=../output/feature_test.RData) }
Step 4: Train a classification model with training features and responses
Call the train model and test model from library.
train.R and test.R should be wrappers for all your model training steps and your classification/prediction steps.
train.R
- Input: a data frame containing features and labels and a parameter list.
- Output:a trained model
test.R
- Input: the fitted classification model using training data and processed features from testing images
- Input: an R object that contains a trained classifier.
Output: training model specification
In this Starter Code, we use logistic regression with LASSO penalty to do classification.
rr source(../lib/train.R) source(../lib/test.R) source(../lib/train_gbm.R) source(../lib/test_gbm.R) source(../lib/train_SVM.R) source(../lib/test_SVM.R) source(../lib/fit_train_xgboost.R) source(../lib/fit_train_randomforest.R)
Model selection with cross-validation
rr feature_train = as.matrix(dat_train[, -6007]) label_train = as.integer(dat_train$label)
source(../lib/cross_validation.R) source(../lib/cross_validation_SVM.R) source(../lib/cv_gbm.R)
if(run.cv.baseline){
mean_error_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage)) sd_error_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage)) mean_auc_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage)) sd_auc_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))
for(i in 1:length(n.trees)){ cat(.trees =, n.trees[i],\n) for(k in 1:length(shrinkage)){ cat(=, shrinkage[k],\n)
res_cv_gbm <- cv_gbm(features = feature_train, labels = label_train, K, n.trees = n.trees[i],shrinkage = shrinkage[k],reweight = sample.reweight)
mean_error_cv[i,k]<-res_cv_gbm[1]
sd_error_cv[i,k]<-res_cv_gbm[2]
mean_auc_cv[i,k]<-res_cv_gbm[3]
sd_auc_cv[i,k]<-res_cv_gbm[4]
save(mean_error_cv, file=\../output/mean_error_cv.RData\)
save(sd_error_cv, file=\../output/sd_error_cv.RData\)
save(mean_auc_cv, file=\../output/mean_auc_cv.RData\)
save(sd_auc_cv, file=\../output/sd_auc_cv.RData\)
}}
} else{ load(../output/mean_error_cv.RData) load(../output/sd_error_cv.RData)
load(../output/mean_auc_cv.RData) load(../output/sd_auc_cv.RData) }
if(run.cv){ res_cv <- matrix(0, nrow = length(lmbd), ncol = 4) for(i in 1:length(lmbd)){ cat(= , lmbd[i], \n) res_cv[i,] <- cv.function(features = feature_train, labels = label_train, K, l = lmbd[i], reweight = sample.reweight) save(res_cv, file=../output/res_cv.RData) } }else{ load(../output/res_cv.RData) }
rr if(run.cv.svm){ res_cv_svm <- matrix(0, nrow = length(cost), ncol = 4) for(i in 1:length(cost)){ cat(= , cost[i], \n) res_cv_svm[i,] <- svm_cv(features = feature_train, labels = label_train, K, cost=cost[i], reweight = sample.reweight) save(res_cv_svm, file=../output/res_cv_svm.RData) } }else{ load(../output/res_cv_svm.RData) }
rr source(../lib/xgboost_cv.R) feature_train = as.matrix(dat_train[, -6007]) label_train = as.integer(dat_train$label) label_train_xgb <- label_train label_train_xgb[label_train_xgb == 2] <- 0 set_rounds <- 50 K <- 5 new_params <- cv_xgboost(params, feature_train, label_train_xgb, set_rounds, K)
[1] train-error:0.102605+0.007682 test-error:0.221244+0.013096
[11] train-error:0.005313+0.001335 test-error:0.178322+0.017084
[21] train-error:0.000000+0.000000 test-error:0.173332+0.013634
[31] train-error:0.000000+0.000000 test-error:0.169574+0.016910
[41] train-error:0.000000+0.000000 test-error:0.171246+0.015282
[50] train-error:0.000000+0.000000 test-error:0.169996+0.013158
rr library(tidyr)
df_mean_error=data.frame(mean_error_cv)%>% setNames(shrinkage)%>% mutate(n.trees=n.trees)%>% gather(shrinkage,mean_error,0.01:0.15)
df_sd_error=data.frame(sd_error_cv)%>% setNames(shrinkage)%>% mutate(n.trees=n.trees)%>% gather(shrinkage,sd_error,0.01:0.15)
df_mean_auc=data.frame(mean_auc_cv)%>% setNames(shrinkage)%>% mutate(n.trees=n.trees)%>% gather(shrinkage,mean_auc,0.01:0.15)
df_sd_auc=data.frame(sd_auc_cv)%>% setNames(shrinkage)%>% mutate(n.trees=n.trees)%>% gather(shrinkage,sd_auc,0.01:0.15)
res_cv_gbm <- df_mean_error%>%mutate(sd_error=df_sd_error\(sd_error, mean_auc=df_mean_auc\)mean_auc, sd_auc=df_sd_auc$sd_auc) save(res_cv_gbm,file = ../output/res_cv_gbm.RData)
Visualize cross-validation results.
rr load(../output/res_cv_gbm.RData)
if(run.cv.baseline){ p1 <- res_cv_gbm %>% ggplot(aes(x = n.trees, y = mean_error, ymin = mean_error - sd_error, ymax = mean_error +sd_error)) + geom_crossbar() + facet_wrap(~shrinkage) + theme(axis.text.x = element_text(angle = 90, hjust = 1))
p2 <- res_cv_gbm %>% ggplot(aes(x = n.trees, y = mean_auc, ymin = mean_auc - sd_auc, ymax = mean_auc + sd_auc)) + facet_wrap(~shrinkage) + geom_crossbar() + theme(axis.text.x = element_text(angle = 90, hjust = 1))
print(p1) print(p2) }
res_cv_svm <- as.data.frame(res_cv_svm) colnames(res_cv_svm) <- c(_error, _error, _AUC, _AUC) res_cv_svm$k = as.factor(cost) run.cv.svm <- TRUE if(run.cv.svm){ p1 <- res_cv_svm %>% ggplot(aes(x = as.factor(cost), y = mean_error, ymin = mean_error - sd_error, ymax = mean_error + sd_error)) + geom_crossbar() + theme(axis.text.x = element_text(angle = 90, hjust = 1))
p2 <- res_cv_svm %>% ggplot(aes(x = as.factor(cost), y = mean_AUC, ymin = mean_AUC - sd_AUC, ymax = mean_AUC + sd_AUC)) + geom_crossbar() + theme(axis.text.x = element_text(angle = 90, hjust = 1))
print(p1) print(p2) }
- Choose the "best" parameter value for baseline model
rr best_n.trees = as.numeric(res_cv_gbm[which.min(res_cv_gbm$mean_error),1]) best_shrinkage = as.numeric(res_cv_gbm[which.min(res_cv_gbm$mean_error),2])
best_cost <- cost[which.min(res_cv_svm$mean_error)]
- Choose "best" number of trees and mtry for random forest model
rr source(../lib/rf_param_choice.R) feature_train = as.matrix(dat_train[, -6007]) label_train = as.integer(dat_train$label) init_mtry <- sqrt(ncol(dat_train)) rf_opt_tree <- param_choice_rf(feature_train = feature_train, label_train = label_train, mtry = init_mtry, ntree = ntrees)
Train different models
- Train the baseline model with the entire training set using the selected model (model parameter) via cross-validation.
rr # training weights weight_train <- rep(NA, length(label_train)) for (v in unique(label_train)){ weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v]) } if (sample.reweight){ tm_train_baseline <- system.time(fit_train_baseline <- train_gbm(feature_train, label_train, w = weight_train,best_n.trees, best_shrinkage)) } else { tm_train_baseline <- system.time(fit_train_baseline <- train_gbm(feature_train, label_train, w = NULL, best_n.trees, best_shrinkage)) } save(fit_train_baseline, file=../output/fit_train_baseline.RData)
- Train the SVM model with the entire training set using the selected model (model parameter) via cross-validation.
rr weight_train <- rep(NA, length(label_train)) for (v in unique(label_train)){ weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v]) } if (sample.reweight){ tm_train_svm <- system.time(fit_train_svm <-svm_train(feature_train, label_train, w = weight_train, best_cost)) } else { tm_train_svm <- system.time(fit_train_svm <-svm_train(feature_train, label_train, w = NULL, best_cost)) } save(fit_train_svm, file=../output/fit_train_svm.RData)
- Train the XGBoost model with optimal parameters
rr xgb_train_time <- system.time(fit_train_xgb <- xgboost_train(features = feature_train, labels = label_train_xgb, params = new_params, rounds = set_rounds))
[22:46:58] WARNING: amalgamation/../src/learner.cc:541:
Parameters: { early_stop_round, silent } might not be used.
This may not be accurate due to some parameters are only used in language bindings but
passed down to XGBoost core. Or some parameters are not used but slip through this
verification. Please open an issue if you find above cases.
[1] train-error:0.116250
[2] train-error:0.091250
[3] train-error:0.080833
[4] train-error:0.070417
[5] train-error:0.059167
[6] train-error:0.050000
[7] train-error:0.040000
[8] train-error:0.031667
[9] train-error:0.025833
[10] train-error:0.020417
[11] train-error:0.015000
[12] train-error:0.011667
[13] train-error:0.007500
[14] train-error:0.003750
[15] train-error:0.002500
[16] train-error:0.001667
[17] train-error:0.001667
[18] train-error:0.000417
[19] train-error:0.000000
[20] train-error:0.000000
[21] train-error:0.000000
[22] train-error:0.000000
[23] train-error:0.000000
[24] train-error:0.000000
[25] train-error:0.000000
[26] train-error:0.000000
[27] train-error:0.000000
[28] train-error:0.000000
[29] train-error:0.000000
[30] train-error:0.000000
[31] train-error:0.000000
[32] train-error:0.000000
[33] train-error:0.000000
[34] train-error:0.000000
[35] train-error:0.000000
[36] train-error:0.000000
[37] train-error:0.000000
[38] train-error:0.000000
[39] train-error:0.000000
[40] train-error:0.000000
[41] train-error:0.000000
[42] train-error:0.000000
[43] train-error:0.000000
[44] train-error:0.000000
[45] train-error:0.000000
[46] train-error:0.000000
[47] train-error:0.000000
[48] train-error:0.000000
[49] train-error:0.000000
[50] train-error:0.000000
rr save(fit_train_xgb, file=../output/fit_train_xgb.RData)
- Train the Random Forest model with optimal parameters
rr load(file = ../output/res_oob_rf.RData) rf_train_time <- system.time(fit_train_randomforest <- rf_train(feature_train = feature_train, label_train = label_train, mtry = rf_opt_tree\(mtry, ntree = rf_opt_tree\)ntree)) save(fit_train_rf, file=../output/fit_train_rf.RData)
Error in save(fit_train_rf, file = \../output/fit_train_rf.RData\) :
目标对象‘fit_train_rf’不存在
rr #PCA for training features source(../lib/train_PCA.R) # make dat_train a numeric data frame dat_train.new <- matrix(0, ncol = ncol(dat_train) - 1, nrow = nrow(dat_train)) for (i in 1:(ncol(dat_train) - 1)) { dat_train.new[,i] <- as.numeric(dat_train[[i]]) } dat_train.new <- as.data.frame(dat_train.new) #PCA for training features tm_train_pca <- system.time(fit_train_pca <- train_pca(dat_train.new)) save(fit_train_pca, file=../output/pca_train.RData) # determine the important principle components screeplot(fit_train_pca, type = )

rr # The proportion of variance for first 300 PCs sum((fit_train_pca\(sdev[1:300])^2) / sum((fit_train_pca\)sdev)^2)
[1] 0.998993
Therefore, we can choose the first 300 principle components because they explain most of the total variance, which is around 99.9%.
rr # get the features of principle components with emotion index dat_train_pca <- data.frame(fit_train_pca$x[,1:300], emotion_idx = dat_train[,6007])
Use trained PCA model to test data
rr source(../lib/test_PCA.R) dat_test.new <- dat_test colnames(dat_test.new) <- c(colnames(dat_train.new), _idx) tm_test_pca <- system.time(dat_test.new <- test_pca(fit_train_pca, dat_test.new)) #features of testing principle components with the emotion index dat_test_pca <- data.frame(dat_test.new[,1:300], emotion_idx = dat_test[,6007]) save(dat_train_pca, file=../output/feature_train_pca.RData) save(dat_test_pca, file=../output/feature_test_pca.RData)
Apply LDA model
rr #load(../output/feature_train_pca.RData) #train LDA model source(../lib/train_LDA.R) tm_train_lda <- system.time(fit_train_lda <- train_lda(dat_train_pca))
载入程辑包:‘MASS’
The following object is masked from ‘package:dplyr’:
select
rr save(fit_train_lda, file=../output/LDA_train.RData) #training accuracy in LDA model source(../lib/test_LDA.R) pred_train_lda <- test_lda(fit_train_lda, dat_train_pca) accu_train_lda <- mean(pred_train_lda == dat_train_pca$emotion_idx) #cat(trainig accuracy of model LDA, , accu_train_lda*100, %.) #cat(for training model LDA = , tm_train_lda[1], )
Step 5: Run test on test images
*Baseline model
rr tm_test_baseline = NA feature_test <- as.matrix(dat_test[, -6007]) if(run.test){ load(file=../output/fit_train_baseline.RData) tm_test_baseline <- system.time({label_pred_baseline <- as.integer(test_gbm(fit_train_baseline,feature_test,best_n.trees, best_shrinkage, pred.type = 'link')); prob_pred_baseline <- test_gbm(fit_train_baseline, feature_test,best_n.trees, best_shrinkage, pred.type = 'response')}) }
*SVM model
rr tm_test = NA feature_test <- as.matrix(dat_test[, -6007]) if(run.test.svm){ load(file=../output/fit_train_svm.RData) tm_test_svm <- system.time({ label_pred_svm <- as.integer(svm_test(fit_train_svm, feature_test, pred.type = 'class')); prob_pred_svm <- svm_test(fit_train_svm, feature_test, pred.type = 'response')}) }
*XGBoost
rr tm_test_xgb = NA feature_test <- as.matrix(dat_test[, -6007]) if(run.test){ load(file=../output/fit_train_xgb.RData) tm_test_xgb <- system.time({label_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'class'); label_pred_xgb[label_pred_xgb >= 0.5] <- 1; label_pred_xgb[label_pred_xgb < 0.5] <- 0; prob_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'response')}) }
*Random Forest
rr tm_test_rf = NA feature_test <- as.matrix(dat_test[, -6007]) if(run.test){ load(file=../output/fit_train_rf.RData) tm_test_rf <- system.time({label_pred_rf <- predict(fit_train_randomforest, feature_test, pred.type = 'class'); prob_pred_rf <- predict(fit_train_randomforest, feature_test, type = 'prob')}) }
*LDA
rr source(../lib/test_LDA.R) #load(../output/feature_test_pca.RData) load(file=../output/LDA_train.RData) tm_test_lda <- system.time(pred_lda <- test_lda(fit_train_lda, dat_test_pca))
Evaluation
rr ## reweight the test data to represent a balanced label distribution label_test <- as.integer(dat_test$label) weight_test <- rep(NA, length(label_test)) for (v in unique(label_test)){ weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v]) }
accu_baseline <- mean(label_pred_baseline == label_test) tpr.fpr.baseline <- WeightedROC(prob_pred_baseline, label_test, weight_test) auc_baseline <- WeightedAUC(tpr.fpr.baseline)
cat(accuracy of model GBM: with n.trees=,best_n.trees,shrinkage =, best_shrinkage, , accu_baseline*100, %.) cat(AUC of model GBM: with n.trees=, best_n.trees,shrinkage =, best_shrinkage, , auc_baseline, .)
rr label_test <- as.integer(dat_test$label) weight_test <- rep(NA, length(label_test)) for (v in unique(label_test)){ weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v]) }
accu_svm <- mean(label_pred_svm == label_test) tpr.fpr.svm <- WeightedROC(prob_pred_svm, label_test, weight_test) auc_svm <- WeightedAUC(tpr.fpr.svm) cat(accuracy of model:, model_labels_svm[which.min(res_cv_svm$mean_error)], , accu_svm*100, %.) cat(AUC of model:, model_labels_svm[which.min(res_cv_svm$mean_error)], , auc_svm, .)
rr label_test <- as.integer(dat_test$label) label_test_xgb <- label_test label_test_xgb[label_test_xgb==2] = 0 accu_xgb <- mean((label_pred_xgb == label_test_xgb)) tpr.fpr_xgb <- WeightedROC(prob_pred_xgb, label_test_xgb) auc_xgb <- WeightedAUC(tpr.fpr_xgb)
cat(accuracy of the XGBoost model:, , accu_xgb*100, %.) cat(AUC of the XGBoost model:, , auc_xgb, .)
rr label_test <- as.integer(dat_test$label) accu_rf <- mean(label_pred_rf == label_test) tpr.fpr.rf <- WeightedROC(prob_pred_rf[,2], label_test) auc_rf <- WeightedAUC(tpr.fpr.rf)
cat(accuracy of the Random Forest model:, , accu_rf*100, %.) cat(AUC of the Random Forest model:, , auc_rf, .)
rr accu_lda <- mean(dat_test_pca\(emotion_idx == pred_lda) label_lda <- as.numeric(dat_test_pca\)emotion_idx) tpr.fpr.lda <- WeightedROC(as.numeric(pred_lda), label_lda) auc_lda <- WeightedAUC(tpr.fpr.lda) cat(accuracy of the PCA+LDA model:, , accu_lda*100, %.)
The accuracy of the PCA+LDA model: is 82.33333 %.
rr cat(AUC of the PCA+LDA model:, , auc_lda, .)
The AUC of the PCA+LDA model: is 0.6773504 .
Summarize Running Time
Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.
rr cat(for constructing training features=, tm_feature_train[1], ) cat(for constructing testing features=, tm_feature_test[1], ) cat(for training model=, tm_train_baseline[1], ) cat(for testing model=, tm_test_baseline[1], )
rr cat(for constructing training features=, tm_feature_train[1], ) cat(for constructing testing features=, tm_feature_test[1], ) cat(for training model=, tm_train_svm[1], ) cat(for testing model=, tm_test_svm[1], )
rr cat(for constructing training features=, tm_feature_train[1], ) cat(for constructing testing features=, tm_feature_test[1], ) cat(for training model=, xgb_train_time[1], ) cat(for testing model=, tm_test_xgb[1], )
rr cat(for constructing training features=, tm_feature_train[1], ) cat(for constructing testing features=, tm_feature_test[1], ) cat(for training random forest model=, rf_train_time[1], ) cat(for testing random forest model=, tm_test_rf[1], )
rr cat(for constructing training features=, tm_feature_train[1], )
Time for constructing training features= 1.402 s
rr cat(for constructing testing features=, tm_feature_test[1], )
Time for constructing testing features= 0.3 s
rr cat(for training PCA =, tm_train_pca[1], )
Time for training PCA = 325.249 s
rr cat(for testing PCA =, tm_test_pca[1], )
Time for testing PCA = 8.124 s
rr cat(for training model LDA = , tm_train_lda[1], )
Time for training model LDA = 1.927 s
rr cat(for testing model LDA = ,tm_test_lda[1], )
Time for testing model LDA = 0.033 s
Reference
- Du, S., Tao, Y., & Martinez, A. M. (2014). Compound facial expressions of emotion. Proceedings of the National Academy of Sciences, 111(15), E1454-E1462.
LS0tCnRpdGxlOiAiTWFpbiIKYXV0aG9yOiAiRGFpenkgTGFtLCBQZXRlciBLd2F1aywgUWl6aGVuIFlhbmcsIEVsbGVuIENoZW4sIERhcnlsIEtvdyIKb3V0cHV0OgogIHBkZl9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpJbiB5b3VyIGZpbmFsIHJlcG8sIHRoZXJlIHNob3VsZCBiZSBhbiBSIG1hcmtkb3duIGZpbGUgdGhhdCBvcmdhbml6ZXMgKiphbGwgY29tcHV0YXRpb25hbCBzdGVwcyoqIGZvciBldmFsdWF0aW5nIHlvdXIgcHJvcG9zZWQgRmFjaWFsIEV4cHJlc3Npb24gUmVjb2duaXRpb24gZnJhbWV3b3JrLiAKClRoaXMgZmlsZSBpcyBjdXJyZW50bHkgYSB0ZW1wbGF0ZSBmb3IgcnVubmluZyBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLiBZb3Ugc2hvdWxkIHVwZGF0ZSBpdCBhY2NvcmRpbmcgdG8geW91ciBjb2RlcyBidXQgZm9sbG93aW5nIHByZWNpc2VseSB0aGUgc2FtZSBzdHJ1Y3R1cmUuIAoKYGBge3IgbWVzc2FnZT1GQUxTRX0KaWYoIXJlcXVpcmUoIkVCSW1hZ2UiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJFQkltYWdlIikKfQppZighcmVxdWlyZSgiUi5tYXRsYWIiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiUi5tYXRsYWIiKQp9CmlmKCFyZXF1aXJlKCJyZWFkeGwiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikKfQoKaWYoIXJlcXVpcmUoImRwbHlyIikpewogIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikKfQppZighcmVxdWlyZSgicmVhZHhsIikpewogIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCn0KCmlmKCFyZXF1aXJlKCJnZ3Bsb3QyIikpewogIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQp9CgppZighcmVxdWlyZSgiY2FyZXQiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQp9CgppZighcmVxdWlyZSgiZ2xtbmV0IikpewogIGluc3RhbGwucGFja2FnZXMoImdsbW5ldCIpCn0KCmlmKCFyZXF1aXJlKCJXZWlnaHRlZFJPQyIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJXZWlnaHRlZFJPQyIpCn0KCmlmKCFyZXF1aXJlKCJlMTA3MSIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJlMTA3MSIpCn0KCmlmKCFyZXF1aXJlKCJ4Z2Jvb3N0IikpewogIGluc3RhbGwucGFja2FnZXMoInhnYm9vc3QiKQp9CgppZighcmVxdWlyZSgicmFuZG9tRm9yZXN0IikpewogIGluc3RhbGwucGFja2FnZXMoInJhbmRvbUZvcmVzdCIpCn0KCmxpYnJhcnkoUi5tYXRsYWIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KEVCSW1hZ2UpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShjYXJldCkKbGlicmFyeShnbG1uZXQpCmxpYnJhcnkoV2VpZ2h0ZWRST0MpCmxpYnJhcnkoZTEwNzEpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmBgYAoKIyMjIFN0ZXAgMCBzZXQgd29yayBkaXJlY3RvcmllcwpgYGB7ciB3a2RpciwgZXZhbD1GQUxTRX0Kc2V0LnNlZWQoMjAyMCkKIyBzZXR3ZCgifi9Qcm9qZWN0My1GYWNpYWxFbW90aW9uUmVjb2duaXRpb24vZG9jIikKIyBoZXJlIHJlcGxhY2UgaXQgd2l0aCB5b3VyIG93biBwYXRoIG9yIG1hbnVhbGx5IHNldCBpdCBpbiBSU3R1ZGlvIHRvIHdoZXJlIHRoaXMgcm1kIGZpbGUgaXMgbG9jYXRlZC4gCiMgdXNlIHJlbGF0aXZlIHBhdGggZm9yIHJlcHJvZHVjaWJpbGl0eQpgYGAKClByb3ZpZGUgZGlyZWN0b3JpZXMgZm9yIHRyYWluaW5nIGltYWdlcy4gVHJhaW5pbmcgaW1hZ2VzIGFuZCBUcmFpbmluZyBmaWR1Y2lhbCBwb2ludHMgd2lsbCBiZSBpbiBkaWZmZXJlbnQgc3ViZm9sZGVycy4gCmBgYHtyfQp0cmFpbl9kaXIgPC0gIi4uL2RhdGEvdHJhaW5fc2V0LyIgIyBUaGlzIHdpbGwgYmUgbW9kaWZpZWQgZm9yIGRpZmZlcmVudCBkYXRhIHNldHMuCnRyYWluX2ltYWdlX2RpciA8LSBwYXN0ZSh0cmFpbl9kaXIsICJpbWFnZXMvIiwgc2VwPSIiKQp0cmFpbl9wdF9kaXIgPC0gcGFzdGUodHJhaW5fZGlyLCAgInBvaW50cy8iLCBzZXA9IiIpCnRyYWluX2xhYmVsX3BhdGggPC0gcGFzdGUodHJhaW5fZGlyLCAibGFiZWwuY3N2Iiwgc2VwPSIiKSAKYGBgCgojIyMgU3RlcCAxOiBzZXQgdXAgY29udHJvbHMgZm9yIGV2YWx1YXRpb24gZXhwZXJpbWVudHMuCgpJbiB0aGlzIGNodW5rLCB3ZSBoYXZlIGEgc2V0IG9mIGNvbnRyb2xzIGZvciB0aGUgZXZhbHVhdGlvbiBleHBlcmltZW50cy4gCgorIChUL0YpIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIHRyYWluaW5nIHNldAorIChUL0YpIHJld2VpZ2h0aW5nIHRoZSBzYW1wbGVzIGZvciB0cmFpbmluZyBzZXQgCisgKG51bWJlcikgSywgdGhlIG51bWJlciBvZiBDViBmb2xkcworIChUL0YpIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRyYWluaW5nIHNldAorIChUL0YpIHJ1biBldmFsdWF0aW9uIG9uIGFuIGluZGVwZW5kZW50IHRlc3Qgc2V0CisgKFQvRikgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdGVzdCBzZXQKCmBgYHtyIGV4cF9zZXR1cH0KcnVuLmN2IDwtIEZBTFNFICMgcnVuIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIHRyYWluaW5nIHNldApydW4uY3YuYmFzZWxpbmUgPC0gRkFMU0UgIyBydW4gY3Jvc3MtdmFsaWRhdGlvbiBvbiB0aGUgZ2JtIGJhc2VsaW5lCnNhbXBsZS5yZXdlaWdodCA8LSBUUlVFICMgcnVuIHNhbXBsZSByZXdlaWdodGluZyBpbiBtb2RlbCB0cmFpbmluZwpLIDwtIDUgICMgbnVtYmVyIG9mIENWIGZvbGRzCnJ1bi5mZWF0dXJlLnRyYWluIDwtIFRSVUUgIyBwcm9jZXNzIGZlYXR1cmVzIGZvciB0cmFpbmluZyBzZXQKcnVuLnRlc3QgPC0gVFJVRSAjIHJ1biBldmFsdWF0aW9uIG9uIGFuIGluZGVwZW5kZW50IHRlc3Qgc2V0CnJ1bi5mZWF0dXJlLnRlc3QgPC0gVFJVRSAjIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRlc3Qgc2V0CnJ1bi5jdi5zdm0gPC0gRkFMU0UKcnVuLnRlc3Quc3ZtIDwtIFRSVUUKCmBgYAoKVXNpbmcgY3Jvc3MtdmFsaWRhdGlvbiBvciBpbmRlcGVuZGVudCB0ZXN0IHNldCBldmFsdWF0aW9uLCB3ZSBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBtb2RlbHMgd2l0aCBkaWZmZXJlbnQgc3BlY2lmaWNhdGlvbnMuIEluIHRoaXMgU3RhcnRlciBDb2RlLCB3ZSB0dW5lIHBhcmFtZXRlciBsYW1iZGEgKHRoZSBhbW91bnQgb2Ygc2hyaW5rYWdlKSBmb3IgbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIExBU1NPIHBlbmFsdHkuCgpgYGB7ciBtb2RlbF9zZXR1cH0KbG1iZCA9IGMoMWUtMywgNWUtMywgMWUtMiwgNWUtMiwgMWUtMSkKbW9kZWxfbGFiZWxzID0gcGFzdGUoIkxBU1NPIFBlbmFsdHkgd2l0aCBsYW1iZGEgPSIsIGxtYmQpCgojZ2JtIHBhcmFtZXRlcnMgdHVuaW5nOgpuLnRyZWVzID0gYygxMCw1MCwxMDAsMjAwKQpzaHJpbmthZ2UgPSBjKDAuMDEsMC4wNSwwLjEsMC4xNSkKCiNzdm0gcGFyYW1ldGVycyB0dW5pbmc6CmNvc3QgPSBjKDAuMDAwMDAwMSwwLjAwMDAwMSwwLjAwMDAxLDAuMDAwMSwwLjAwMSwwLjAxLDEpCm1vZGVsX2xhYmVsc19zdm0gPSBwYXN0ZSgiU1ZNIHdpdGggY29zdCA9IiwgY29zdCkKCiN4Z2Jvb3N0IHBhcmFtZXRlcnMgdHVuaW5nCnBhcmFtcyA8LSBsaXN0KGJvb3N0ZXIgPSAiZ2J0cmVlIiwgb2JqZWN0aXZlID0gImJpbmFyeTpsb2dpc3RpYyIsIAogICAgICAgICAgICAgICAgIGV0YT0wLjMsIGdhbW1hPTAsIG1heF9kZXB0aD02LCBtaW5fY2hpbGRfd2VpZ2h0PTEsIAogICAgICAgICAgICAgICAgIHN1YnNhbXBsZT0xLCBjb2xzYW1wbGVfYnl0cmVlPTEpCgojcmFuZG9tIGZvcmVzdCBwYXJhbWV0ZXJzIHR1bmluZwpudHJlZXMgPC0gMTI4IAojYWNjb3JkaW5nIHRvIGEgcGFwZXIgYnkgVGhhaXMgTWF5dW1pIE9zaGlybywgUGVkcm8gU2FudG9ybyBQZXJleiwgYW5kIEpvcyDMgWUgQXVndXN0byBCYXJhbmF1c2thLAojIHRoZSBBVUMgZ2FpbiBmb3IgaW5jcmVhc2luZyBudW1iZXIgb2YgdHJlZXMgaXMgbWluaW1hbCBhZnRlciAxMjgsIAojIGFmdGVyIG9ic2VydmluZyBhIG11bHRpdHVkZSBvZiB0aGVpciBkYXRhc2V0cwoKCmBgYAoKIyMjIFN0ZXAgMjogaW1wb3J0IGRhdGEgYW5kIHRyYWluLXRlc3Qgc3BsaXQgCmBgYHtyfQojdHJhaW4tdGVzdCBzcGxpdAppbmZvIDwtIHJlYWQuY3N2KHRyYWluX2xhYmVsX3BhdGgpCm4gPC0gbnJvdyhpbmZvKQpuX3RyYWluIDwtIHJvdW5kKG4qKDQvNSksIDApCnRyYWluX2lkeCA8LSBzYW1wbGUoaW5mbyRJbmRleCwgbl90cmFpbiwgcmVwbGFjZSA9IEYpCnRlc3RfaWR4IDwtIHNldGRpZmYoaW5mbyRJbmRleCwgdHJhaW5faWR4KQpgYGAKCklmIHlvdSBjaG9vc2UgdG8gZXh0cmFjdCBmZWF0dXJlcyBmcm9tIGltYWdlcywgc3VjaCBhcyB1c2luZyBHYWJvciBmaWx0ZXIsIFIgbWVtb3J5IHdpbGwgZXhoYXVzdCBhbGwgaW1hZ2VzIGFyZSByZWFkIHRvZ2V0aGVyLiBUaGUgc29sdXRpb24gaXMgdG8gcmVwZWF0IHJlYWRpbmcgYSBzbWFsbGVyIGJhdGNoKGUuZyAxMDApIGFuZCBwcm9jZXNzIHRoZW0uIApgYGB7cn0Kbl9maWxlcyA8LSBsZW5ndGgobGlzdC5maWxlcyh0cmFpbl9pbWFnZV9kaXIpKQoKaW1hZ2VfbGlzdCA8LSBsaXN0KCkKZm9yKGkgaW4gMToxMDApewogICBpbWFnZV9saXN0W1tpXV0gPC0gcmVhZEltYWdlKHBhc3RlMCh0cmFpbl9pbWFnZV9kaXIsIHNwcmludGYoIiUwNGQiLCBpKSwgIi5qcGciKSkKfQpgYGAKCkZpZHVjaWFsIHBvaW50cyBhcmUgc3RvcmVkIGluIG1hdGxhYiBmb3JtYXQuIEluIHRoaXMgc3RlcCwgd2UgcmVhZCB0aGVtIGFuZCBzdG9yZSB0aGVtIGluIGEgbGlzdC4KYGBge3IgcmVhZCBmaWR1Y2lhbCBwb2ludHN9CiNmdW5jdGlvbiB0byByZWFkIGZpZHVjaWFsIHBvaW50cwojaW5wdXQ6IGluZGV4CiNvdXRwdXQ6IG1hdHJpeCBvZiBmaWR1Y2lhbCBwb2ludHMgY29ycmVzcG9uZGluZyB0byB0aGUgaW5kZXgKcmVhZE1hdC5tYXRyaXggPC0gZnVuY3Rpb24oaW5kZXgpewogICAgIHJldHVybihyb3VuZChyZWFkTWF0KHBhc3RlMCh0cmFpbl9wdF9kaXIsIHNwcmludGYoIiUwNGQiLCBpbmRleCksICIubWF0IikpW1sxXV0sMCkpCn0KCiNsb2FkIGZpZHVjaWFsIHBvaW50cwpmaWR1Y2lhbF9wdF9saXN0IDwtIGxhcHBseSgxOm5fZmlsZXMsIHJlYWRNYXQubWF0cml4KQpzYXZlKGZpZHVjaWFsX3B0X2xpc3QsIGZpbGU9Ii4uL291dHB1dC9maWR1Y2lhbF9wdF9saXN0LlJEYXRhIikKYGBgCgojIyMgU3RlcCAzOiBjb25zdHJ1Y3QgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcwoKKyBUaGUgZm9sbG93IHBsb3RzIHNob3cgaG93IHBhaXJ3aXNlIGRpc3RhbmNlIGJldHdlZW4gZmlkdWNpYWwgcG9pbnRzIGNhbiB3b3JrIGFzIGZlYXR1cmUgZm9yIGZhY2lhbCBlbW90aW9uIHJlY29nbml0aW9uLgoKICArIEluIHRoZSBmaXJzdCBjb2x1bW4sIDc4IGZpZHVjaWFscyBwb2ludHMgb2YgZWFjaCBlbW90aW9uIGFyZSBtYXJrZWQgaW4gb3JkZXIuIAogICsgSW4gdGhlIHNlY29uZCBjb2x1bW4gZGlzdHJpYnV0aW9ucyBvZiB2ZXJ0aWNhbCBkaXN0YW5jZSBiZXR3ZWVuIHJpZ2h0IHB1cGlsKDEpIGFuZCAgcmlnaHQgYnJvdyBwZWFrKDIxKSBhcmUgc2hvd24gaW4gIGhpc3RvZ3JhbXMuIEZvciBleGFtcGxlLCB0aGUgZGlzdGFuY2Ugb2YgYW4gYW5ncnkgZmFjZSB0ZW5kcyB0byBiZSBzaG9ydGVyIHRoYW4gdGhhdCBvZiBhIHN1cnByaXNlZCBmYWNlLgogICsgVGhlIHRoaXJkIGNvbHVtbiBpcyB0aGUgZGlzdHJpYnV0aW9ucyBvZiB2ZXJ0aWNhbCBkaXN0YW5jZXMgYmV0d2VlbiByaWdodCBtb3V0aCBjb3JuZXIoNTApCmFuZCB0aGUgbWlkcG9pbnQgb2YgdGhlIHVwcGVyIGxpcCg1MikuICBGb3IgZXhhbXBsZSwgdGhlIGRpc3RhbmNlIG9mIGFuIGhhcHB5IGZhY2UgdGVuZHMgdG8gYmUgc2hvcnRlciB0aGFuIHRoYXQgb2YgYSBzYWQgZmFjZS4KCiFbRmlndXJlMV0oLi4vZmlncy9mZWF0dXJlX3Zpc3VhbGl6YXRpb24uanBnKQoKYGZlYXR1cmUuUmAgc2hvdWxkIGJlIHRoZSB3cmFwcGVyIGZvciBhbGwgeW91ciBmZWF0dXJlIGVuZ2luZWVyaW5nIGZ1bmN0aW9ucyBhbmQgb3B0aW9ucy4gVGhlIGZ1bmN0aW9uIGBmZWF0dXJlKCApYCBzaG91bGQgaGF2ZSBvcHRpb25zIHRoYXQgY29ycmVzcG9uZCB0byBkaWZmZXJlbnQgc2NlbmFyaW9zIGZvciB5b3VyIHByb2plY3QgYW5kIHByb2R1Y2VzIGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcyB0aGF0IGFyZSByZXF1aXJlZCBieSBhbGwgdGhlIG1vZGVscyB5b3UgYXJlIGdvaW5nIHRvIGV2YWx1YXRlIGxhdGVyLiAKICAKICArIGBmZWF0dXJlLlJgCiAgKyBJbnB1dDogbGlzdCBvZiBpbWFnZXMgb3IgZmlkdWNpYWwgcG9pbnQKICArIE91dHB1dDogYW4gUkRhdGEgZmlsZSB0aGF0IGNvbnRhaW5zIGV4dHJhY3RlZCBmZWF0dXJlcyBhbmQgY29ycmVzcG9uZGluZyByZXNwb25zZXMKCmBgYHtyIGZlYXR1cmV9CnNvdXJjZSgiLi4vbGliL2ZlYXR1cmUuUiIpCnRtX2ZlYXR1cmVfdHJhaW4gPC0gTkEKaWYocnVuLmZlYXR1cmUudHJhaW4pewogIHRtX2ZlYXR1cmVfdHJhaW4gPC0gc3lzdGVtLnRpbWUoZGF0X3RyYWluIDwtIGZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCwgdHJhaW5faWR4KSkKICBzYXZlKGRhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluLlJEYXRhIikKfQoKdG1fZmVhdHVyZV90ZXN0IDwtIE5BCmlmKHJ1bi5mZWF0dXJlLnRlc3QpewogIHRtX2ZlYXR1cmVfdGVzdCA8LSBzeXN0ZW0udGltZShkYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4KSkKICBzYXZlKGRhdF90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0LlJEYXRhIikKfWVsc2V7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0LlJEYXRhIikKfQoKCmBgYAoKIyMjIFN0ZXAgNDogVHJhaW4gYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCB3aXRoIHRyYWluaW5nIGZlYXR1cmVzIGFuZCByZXNwb25zZXMKQ2FsbCB0aGUgdHJhaW4gbW9kZWwgYW5kIHRlc3QgbW9kZWwgZnJvbSBsaWJyYXJ5LiAKCmB0cmFpbi5SYCBhbmQgYHRlc3QuUmAgc2hvdWxkIGJlIHdyYXBwZXJzIGZvciBhbGwgeW91ciBtb2RlbCB0cmFpbmluZyBzdGVwcyBhbmQgeW91ciBjbGFzc2lmaWNhdGlvbi9wcmVkaWN0aW9uIHN0ZXBzLiAKCisgYHRyYWluLlJgCiAgKyBJbnB1dDogYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgZmVhdHVyZXMgYW5kIGxhYmVscyBhbmQgYSBwYXJhbWV0ZXIgbGlzdC4KICArIE91dHB1dDphIHRyYWluZWQgbW9kZWwKKyBgdGVzdC5SYAogICsgSW5wdXQ6IHRoZSBmaXR0ZWQgY2xhc3NpZmljYXRpb24gbW9kZWwgdXNpbmcgdHJhaW5pbmcgZGF0YSBhbmQgcHJvY2Vzc2VkIGZlYXR1cmVzIGZyb20gdGVzdGluZyBpbWFnZXMgCiAgKyBJbnB1dDogYW4gUiBvYmplY3QgdGhhdCBjb250YWlucyBhIHRyYWluZWQgY2xhc3NpZmllci4KICArIE91dHB1dDogdHJhaW5pbmcgbW9kZWwgc3BlY2lmaWNhdGlvbgoKKyBJbiB0aGlzIFN0YXJ0ZXIgQ29kZSwgd2UgdXNlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCBMQVNTTyBwZW5hbHR5IHRvIGRvIGNsYXNzaWZpY2F0aW9uLiAKCmBgYHtyIGxvYWRsaWJ9CnNvdXJjZSgiLi4vbGliL3RyYWluLlIiKSAKc291cmNlKCIuLi9saWIvdGVzdC5SIikKCnNvdXJjZSgiLi4vbGliL3RyYWluX2dibS5SIikKc291cmNlKCIuLi9saWIvdGVzdF9nYm0uUiIpCgpzb3VyY2UoIi4uL2xpYi90cmFpbl9TVk0uUiIpIApzb3VyY2UoIi4uL2xpYi90ZXN0X1NWTS5SIikKCnNvdXJjZSgiLi4vbGliL2ZpdF90cmFpbl94Z2Jvb3N0LlIiKQpzb3VyY2UoIi4uL2xpYi9maXRfdHJhaW5fcmFuZG9tZm9yZXN0LlIiKQpgYGAKCiMjIyMgTW9kZWwgc2VsZWN0aW9uIHdpdGggY3Jvc3MtdmFsaWRhdGlvbgoqIERvIG1vZGVsIHNlbGVjdGlvbiBieSBjaG9vc2luZyBhbW9uZyBkaWZmZXJlbnQgdmFsdWVzIG9mIHRyYWluaW5nIG1vZGVsIHBhcmFtZXRlcnMuCgoKKiBCYXNlbGluZS9HQk0KYGBge3IgcnVuY3Z9CmZlYXR1cmVfdHJhaW4gPSBhcy5tYXRyaXgoZGF0X3RyYWluWywgLTYwMDddKQpsYWJlbF90cmFpbiA9IGFzLmludGVnZXIoZGF0X3RyYWluJGxhYmVsKSAKCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb24uUiIpCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb25fU1ZNLlIiKQpzb3VyY2UoIi4uL2xpYi9jdl9nYm0uUiIpCgppZihydW4uY3YuYmFzZWxpbmUpeyAgCiAgCiAgbWVhbl9lcnJvcl9jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChuLnRyZWVzKSwgbmNvbCA9IGxlbmd0aChzaHJpbmthZ2UpKQogIHNkX2Vycm9yX2N2IDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKG4udHJlZXMpLCBuY29sID0gbGVuZ3RoKHNocmlua2FnZSkpCiAgbWVhbl9hdWNfY3YgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcyksIG5jb2wgPSBsZW5ndGgoc2hyaW5rYWdlKSkKICBzZF9hdWNfY3YgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcyksIG5jb2wgPSBsZW5ndGgoc2hyaW5rYWdlKSkKCiAgZm9yKGkgaW4gMTpsZW5ndGgobi50cmVlcykpewogICAgY2F0KCJuLnRyZWVzID0iLCBuLnRyZWVzW2ldLCJcbiIpCiAgZm9yKGsgaW4gMTpsZW5ndGgoc2hyaW5rYWdlKSl7CiAgICBjYXQoInNocmlua2FnZSA9Iiwgc2hyaW5rYWdlW2tdLCJcbiIpCiAgICAKcmVzX2N2X2dibSA8LSBjdl9nYm0oZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgIG4udHJlZXMgPSBuLnRyZWVzW2ldLHNocmlua2FnZSA9IHNocmlua2FnZVtrXSxyZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkKICAgICAgCiAgICBtZWFuX2Vycm9yX2N2W2ksa108LXJlc19jdl9nYm1bMV0KICAgICBzZF9lcnJvcl9jdltpLGtdPC1yZXNfY3ZfZ2JtWzJdCiAgICAgIG1lYW5fYXVjX2N2W2ksa108LXJlc19jdl9nYm1bM10KICAgICAgIHNkX2F1Y19jdltpLGtdPC1yZXNfY3ZfZ2JtWzRdCiAgICAgIAogICAgc2F2ZShtZWFuX2Vycm9yX2N2LCBmaWxlPSIuLi9vdXRwdXQvbWVhbl9lcnJvcl9jdi5SRGF0YSIpCiAgICBzYXZlKHNkX2Vycm9yX2N2LCBmaWxlPSIuLi9vdXRwdXQvc2RfZXJyb3JfY3YuUkRhdGEiKQogICAgc2F2ZShtZWFuX2F1Y19jdiwgZmlsZT0iLi4vb3V0cHV0L21lYW5fYXVjX2N2LlJEYXRhIikKICAgIHNhdmUoc2RfYXVjX2N2LCBmaWxlPSIuLi9vdXRwdXQvc2RfYXVjX2N2LlJEYXRhIikgCiAgICB9fQogfSBlbHNlewogIGxvYWQoIi4uL291dHB1dC9tZWFuX2Vycm9yX2N2LlJEYXRhIikKICBsb2FkKCIuLi9vdXRwdXQvc2RfZXJyb3JfY3YuUkRhdGEiKSAgIAogIGxvYWQoIi4uL291dHB1dC9tZWFuX2F1Y19jdi5SRGF0YSIpCiAgbG9hZCgiLi4vb3V0cHV0L3NkX2F1Y19jdi5SRGF0YSIpCiAgICB9CgppZihydW4uY3YpewogIHJlc19jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChsbWJkKSwgbmNvbCA9IDQpCiAgZm9yKGkgaW4gMTpsZW5ndGgobG1iZCkpewogICAgY2F0KCJsYW1iZGEgPSAiLCBsbWJkW2ldLCAiXG4iKQogICAgcmVzX2N2W2ksXSA8LSBjdi5mdW5jdGlvbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluLCBLLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IGxtYmRbaV0sIHJld2VpZ2h0ID0gc2FtcGxlLnJld2VpZ2h0KQogIHNhdmUocmVzX2N2LCBmaWxlPSIuLi9vdXRwdXQvcmVzX2N2LlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3YuUkRhdGEiKQp9CmBgYAoKKiBTVk0KYGBge3Igc3ZtfQppZihydW4uY3Yuc3ZtKXsKICByZXNfY3Zfc3ZtIDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKGNvc3QpLCBuY29sID0gNCkKICBmb3IoaSBpbiAxOmxlbmd0aChjb3N0KSl7CiAgICBjYXQoImNvc3Q9ICIsIGNvc3RbaV0sICJcbiIpCiAgICByZXNfY3Zfc3ZtW2ksXSA8LSBzdm1fY3YoZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgY29zdD1jb3N0W2ldLCByZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkKICBzYXZlKHJlc19jdl9zdm0sIGZpbGU9Ii4uL291dHB1dC9yZXNfY3Zfc3ZtLlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3Zfc3ZtLlJEYXRhIikKfQpgYGAKCgoqIFhHQm9vc3QgCmBgYHtyIFBhcmFtZXRlciBPcHRpbWl6YXRpb24gVGhyb3VnaCBDcm9zcyBWYWxpZGF0aW9uIGZvciBYR0Jvb3N0fQoKc291cmNlKCIuLi9saWIveGdib29zdF9jdi5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIApsYWJlbF90cmFpbl94Z2IgPC0gbGFiZWxfdHJhaW4KbGFiZWxfdHJhaW5feGdiW2xhYmVsX3RyYWluX3hnYiA9PSAyXSA8LSAwCgpzZXRfcm91bmRzICA8LSA1MApLIDwtIDUKCm5ld19wYXJhbXMgPC0gY3ZfeGdib29zdChwYXJhbXMsIGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluX3hnYiwgc2V0X3JvdW5kcywgSykKCnNhdmUobmV3X3BhcmFtcywgZmlsZT0iLi4vb3V0cHV0L3Jlc19jdl94Z2Jvb3N0LlJEYXRhIikKCmBgYCAKCmBgYHtyIGN2IHJlc3VsdCBkZn0KbGlicmFyeSh0aWR5cikKCmRmX21lYW5fZXJyb3I9ZGF0YS5mcmFtZShtZWFuX2Vycm9yX2N2KSU+JQpzZXROYW1lcyhzaHJpbmthZ2UpJT4lCm11dGF0ZShuLnRyZWVzPW4udHJlZXMpJT4lCmdhdGhlcihzaHJpbmthZ2UsbWVhbl9lcnJvcixgMC4wMWA6YDAuMTVgKQoKZGZfc2RfZXJyb3I9ZGF0YS5mcmFtZShzZF9lcnJvcl9jdiklPiUKc2V0TmFtZXMoc2hyaW5rYWdlKSU+JQptdXRhdGUobi50cmVlcz1uLnRyZWVzKSU+JQpnYXRoZXIoc2hyaW5rYWdlLHNkX2Vycm9yLGAwLjAxYDpgMC4xNWApCgpkZl9tZWFuX2F1Yz1kYXRhLmZyYW1lKG1lYW5fYXVjX2N2KSU+JQpzZXROYW1lcyhzaHJpbmthZ2UpJT4lCm11dGF0ZShuLnRyZWVzPW4udHJlZXMpJT4lCmdhdGhlcihzaHJpbmthZ2UsbWVhbl9hdWMsYDAuMDFgOmAwLjE1YCkKCmRmX3NkX2F1Yz1kYXRhLmZyYW1lKHNkX2F1Y19jdiklPiUKc2V0TmFtZXMoc2hyaW5rYWdlKSU+JQptdXRhdGUobi50cmVlcz1uLnRyZWVzKSU+JQpnYXRoZXIoc2hyaW5rYWdlLHNkX2F1YyxgMC4wMWA6YDAuMTVgKQoKcmVzX2N2X2dibSA8LSBkZl9tZWFuX2Vycm9yJT4lbXV0YXRlKHNkX2Vycm9yPWRmX3NkX2Vycm9yJHNkX2Vycm9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbl9hdWM9ZGZfbWVhbl9hdWMkbWVhbl9hdWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZF9hdWM9ZGZfc2RfYXVjJHNkX2F1YykKc2F2ZShyZXNfY3ZfZ2JtLGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19jdl9nYm0uUkRhdGEiKQpgYGAKClZpc3VhbGl6ZSBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHMuIApgYGB7ciBjdl92aXN9CmxvYWQoIi4uL291dHB1dC9yZXNfY3ZfZ2JtLlJEYXRhIikKCgppZihydW4uY3YuYmFzZWxpbmUpewogIHAxIDwtIHJlc19jdl9nYm0gJT4lIAogICAgZ2dwbG90KGFlcyh4ID0gbi50cmVlcywgeSA9IG1lYW5fZXJyb3IsCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICArc2RfZXJyb3IpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIGZhY2V0X3dyYXAofnNocmlua2FnZSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKICAKICBwMiA8LSByZXNfY3ZfZ2JtICU+JSAKICAgIGdncGxvdChhZXMoeCA9IG4udHJlZXMsIHkgPSBtZWFuX2F1YywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fYXVjIC0gc2RfYXVjLCB5bWF4ID0gbWVhbl9hdWMgKyBzZF9hdWMpKSArICAgICBmYWNldF93cmFwKH5zaHJpbmthZ2UpICsKICAgIGdlb21fY3Jvc3NiYXIoKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQogIAogIHByaW50KHAxKQogIHByaW50KHAyKQp9CgpyZXNfY3Zfc3ZtIDwtIGFzLmRhdGEuZnJhbWUocmVzX2N2X3N2bSkgCmNvbG5hbWVzKHJlc19jdl9zdm0pIDwtIGMoIm1lYW5fZXJyb3IiLCAic2RfZXJyb3IiLCAibWVhbl9BVUMiLCAic2RfQVVDIikKcmVzX2N2X3N2bSRrID0gYXMuZmFjdG9yKGNvc3QpCnJ1bi5jdi5zdm0gPC0gVFJVRQppZihydW4uY3Yuc3ZtKXsKICBwMSA8LSByZXNfY3Zfc3ZtICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGFzLmZhY3Rvcihjb3N0KSwgeSA9IG1lYW5fZXJyb3IsCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICsgc2RfZXJyb3IpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcDIgPC0gcmVzX2N2X3N2bSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoY29zdCksIHkgPSBtZWFuX0FVQywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fQVVDIC0gc2RfQVVDLCB5bWF4ID0gbWVhbl9BVUMgKyBzZF9BVUMpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcHJpbnQocDEpCiAgcHJpbnQocDIpCn0KYGBgCgoKKiBDaG9vc2UgdGhlICJiZXN0IiBwYXJhbWV0ZXIgdmFsdWUgZm9yIGJhc2VsaW5lIG1vZGVsCmBgYHtyIGJlc3RfbW9kZWx9CmJlc3Rfbi50cmVlcyA9IGFzLm51bWVyaWMocmVzX2N2X2dibVt3aGljaC5taW4ocmVzX2N2X2dibSRtZWFuX2Vycm9yKSwxXSkKYmVzdF9zaHJpbmthZ2UgPSBhcy5udW1lcmljKHJlc19jdl9nYm1bd2hpY2gubWluKHJlc19jdl9nYm0kbWVhbl9lcnJvciksMl0pCgpiZXN0X2Nvc3QgPC0gY29zdFt3aGljaC5taW4ocmVzX2N2X3N2bSRtZWFuX2Vycm9yKV0KYGBgCgoqIENob29zZSAiYmVzdCIgbnVtYmVyIG9mIHRyZWVzIGFuZCBtdHJ5IGZvciByYW5kb20gZm9yZXN0IG1vZGVsCmBgYHtyIGJlc3RfbW9kZWxfcmZ9CnNvdXJjZSgiLi4vbGliL3JmX3BhcmFtX2Nob2ljZS5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIAppbml0X210cnkgPC0gc3FydChuY29sKGRhdF90cmFpbikpCgpyZl9vcHRfdHJlZSA8LSBwYXJhbV9jaG9pY2VfcmYoZmVhdHVyZV90cmFpbiA9IGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluID0gIGxhYmVsX3RyYWluLCBtdHJ5ID0gaW5pdF9tdHJ5LCBudHJlZSA9IG50cmVlcykKCnNhdmUocmZfb3B0X3RyZWUsIGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19vb2JfcmYuUkRhdGEiKQpgYGAKCgojIyMjIFRyYWluIGRpZmZlcmVudCBtb2RlbHMKKiBUcmFpbiB0aGUgYmFzZWxpbmUgbW9kZWwgd2l0aCB0aGUgZW50aXJlIHRyYWluaW5nIHNldCB1c2luZyB0aGUgc2VsZWN0ZWQgbW9kZWwgKG1vZGVsIHBhcmFtZXRlcikgdmlhIGNyb3NzLXZhbGlkYXRpb24uCmBgYHtyIGZpbmFsX3RyYWluX2Jhc2VsaW5lfQojIHRyYWluaW5nIHdlaWdodHMKd2VpZ2h0X3RyYWluIDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3RyYWluKSkKZm9yICh2IGluIHVuaXF1ZShsYWJlbF90cmFpbikpewogIHdlaWdodF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSA9IDAuNSAqIGxlbmd0aChsYWJlbF90cmFpbikgLyBsZW5ndGgobGFiZWxfdHJhaW5bbGFiZWxfdHJhaW4gPT0gdl0pCn0KaWYgKHNhbXBsZS5yZXdlaWdodCl7CiAgdG1fdHJhaW5fYmFzZWxpbmUgPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX2Jhc2VsaW5lIDwtIHRyYWluX2dibShmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbixiZXN0X24udHJlZXMsIGJlc3Rfc2hyaW5rYWdlKSkKfSBlbHNlIHsKICB0bV90cmFpbl9iYXNlbGluZSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fYmFzZWxpbmUgPC0gdHJhaW5fZ2JtKGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluLCB3ID0gTlVMTCwgYmVzdF9uLnRyZWVzLCBiZXN0X3Nocmlua2FnZSkpCn0Kc2F2ZShmaXRfdHJhaW5fYmFzZWxpbmUsIGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5fYmFzZWxpbmUuUkRhdGEiKQpgYGAKCiogVHJhaW4gdGhlIFNWTSBtb2RlbCB3aXRoIHRoZSBlbnRpcmUgdHJhaW5pbmcgc2V0IHVzaW5nIHRoZSBzZWxlY3RlZCBtb2RlbCAobW9kZWwgcGFyYW1ldGVyKSB2aWEgY3Jvc3MtdmFsaWRhdGlvbi4KYGBge3IgZmluYWxfdHJhaW5fc3ZtfQp3ZWlnaHRfdHJhaW4gPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdHJhaW4pKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3RyYWluKSl7CiAgd2VpZ2h0X3RyYWluW2xhYmVsX3RyYWluID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3RyYWluKSAvIGxlbmd0aChsYWJlbF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSkKfQppZiAoc2FtcGxlLnJld2VpZ2h0KXsKICB0bV90cmFpbl9zdm0gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX3N2bSA8LXN2bV90cmFpbihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbiwgYmVzdF9jb3N0KSkKfSBlbHNlIHsKICB0bV90cmFpbl9zdm0gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX3N2bSA8LXN2bV90cmFpbihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IE5VTEwsIGJlc3RfY29zdCkpCn0Kc2F2ZShmaXRfdHJhaW5fc3ZtLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3N2bS5SRGF0YSIpCmBgYAoKCiogVHJhaW4gdGhlIFhHQm9vc3QgbW9kZWwgd2l0aCBvcHRpbWFsIHBhcmFtZXRlcnMKCmBgYHtyIGZpbmFsX3RyYWluX3hnYn0KCnhnYl90cmFpbl90aW1lIDwtIHN5c3RlbS50aW1lKGZpdF90cmFpbl94Z2IgPC0geGdib29zdF90cmFpbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluX3hnYiwgcGFyYW1zID0gbmV3X3BhcmFtcywgcm91bmRzID0gIHNldF9yb3VuZHMpKQoKc2F2ZShmaXRfdHJhaW5feGdiLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3hnYi5SRGF0YSIpCgpgYGAKCiogVHJhaW4gdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwgd2l0aCBvcHRpbWFsIHBhcmFtZXRlcnMKCmBgYHtyIGZpbmFsX3RyYWluX3JmfQpsb2FkKGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19vb2JfcmYuUkRhdGEiKQpyZl90cmFpbl90aW1lIDwtIHN5c3RlbS50aW1lKGZpdF90cmFpbl9yYW5kb21mb3Jlc3QgPC0gcmZfdHJhaW4oZmVhdHVyZV90cmFpbiA9IGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluID0gbGFiZWxfdHJhaW4sIG10cnkgPSByZl9vcHRfdHJlZSRtdHJ5LCBudHJlZSA9IHJmX29wdF90cmVlJG50cmVlKSkKc2F2ZShmaXRfdHJhaW5fcmFuZG9tZm9yZXN0LCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3JmLlJEYXRhIikKYGBgCgoqIHRyYWluIFBDQSArIExEQQpgYGB7ciBQQ0F9CgojUENBIGZvciB0cmFpbmluZyBmZWF0dXJlcwpzb3VyY2UoIi4uL2xpYi90cmFpbl9QQ0EuUiIpCgojIG1ha2UgZGF0X3RyYWluIGEgbnVtZXJpYyBkYXRhIGZyYW1lCmRhdF90cmFpbi5uZXcgPC0gbWF0cml4KDAsIG5jb2wgPSBuY29sKGRhdF90cmFpbikgLSAxLCBucm93ID0gbnJvdyhkYXRfdHJhaW4pKQpmb3IgKGkgaW4gMToobmNvbChkYXRfdHJhaW4pIC0gMSkpIHsKZGF0X3RyYWluLm5ld1ssaV0gPC0gYXMubnVtZXJpYyhkYXRfdHJhaW5bW2ldXSkKfQpkYXRfdHJhaW4ubmV3IDwtIGFzLmRhdGEuZnJhbWUoZGF0X3RyYWluLm5ldykKCiNQQ0EgZm9yIHRyYWluaW5nIGZlYXR1cmVzCnRtX3RyYWluX3BjYSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fcGNhIDwtIHRyYWluX3BjYShkYXRfdHJhaW4ubmV3KSkKCnNhdmUoZml0X3RyYWluX3BjYSwgZmlsZT0iLi4vb3V0cHV0L3BjYV90cmFpbi5SRGF0YSIpCgojIGRldGVybWluZSB0aGUgaW1wb3J0YW50IHByaW5jaXBsZSBjb21wb25lbnRzCnNjcmVlcGxvdChmaXRfdHJhaW5fcGNhLCB0eXBlID0gImwiKQoKIyBUaGUgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBmb3IgZmlyc3QgMzAwIFBDcwpzdW0oKGZpdF90cmFpbl9wY2Ekc2RldlsxOjMwMF0pXjIpIC8gc3VtKChmaXRfdHJhaW5fcGNhJHNkZXYpXjIpCgpgYGAKVGhlcmVmb3JlLCB3ZSBjYW4gY2hvb3NlIHRoZSBmaXJzdCAzMDAgcHJpbmNpcGxlIGNvbXBvbmVudHMgYmVjYXVzZSB0aGV5IGV4cGxhaW4gbW9zdCBvZiB0aGUgdG90YWwgdmFyaWFuY2UsIHdoaWNoIGlzIGFyb3VuZCA5OS45JS4KCmBgYHtyIFBDQSB0cmFpbn0KIyBnZXQgdGhlIGZlYXR1cmVzIG9mIHByaW5jaXBsZSBjb21wb25lbnRzIHdpdGggZW1vdGlvbiBpbmRleApkYXRfdHJhaW5fcGNhIDwtIGRhdGEuZnJhbWUoZml0X3RyYWluX3BjYSR4WywxOjMwMF0sIGVtb3Rpb25faWR4ID0gZGF0X3RyYWluWyw2MDA3XSkKYGBgCgpVc2UgdHJhaW5lZCBQQ0EgbW9kZWwgdG8gdGVzdCBkYXRhCmBgYHtyIFBDQSB0ZXN0fQpzb3VyY2UoIi4uL2xpYi90ZXN0X1BDQS5SIikKZGF0X3Rlc3QubmV3IDwtIGRhdF90ZXN0CmNvbG5hbWVzKGRhdF90ZXN0Lm5ldykgPC0gYyhjb2xuYW1lcyhkYXRfdHJhaW4ubmV3KSwgImVtb3Rpb25faWR4IikKCnRtX3Rlc3RfcGNhIDwtIHN5c3RlbS50aW1lKGRhdF90ZXN0Lm5ldyA8LSB0ZXN0X3BjYShmaXRfdHJhaW5fcGNhLCBkYXRfdGVzdC5uZXcpKQoKI2ZlYXR1cmVzIG9mIHRlc3RpbmcgcHJpbmNpcGxlIGNvbXBvbmVudHMgd2l0aCB0aGUgZW1vdGlvbiBpbmRleApkYXRfdGVzdF9wY2EgPC0gZGF0YS5mcmFtZShkYXRfdGVzdC5uZXdbLDE6MzAwXSwgZW1vdGlvbl9pZHggPSBkYXRfdGVzdFssNjAwN10pCgpzYXZlKGRhdF90cmFpbl9wY2EsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluX3BjYS5SRGF0YSIpCnNhdmUoZGF0X3Rlc3RfcGNhLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0X3BjYS5SRGF0YSIpCmBgYAoKQXBwbHkgTERBIG1vZGVsCgpgYGB7ciBMREEgdHJhaW59CiNsb2FkKCIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbl9wY2EuUkRhdGEiKQoKI3RyYWluIExEQSBtb2RlbApzb3VyY2UoIi4uL2xpYi90cmFpbl9MREEuUiIpCnRtX3RyYWluX2xkYSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fbGRhIDwtIHRyYWluX2xkYShkYXRfdHJhaW5fcGNhKSkKc2F2ZShmaXRfdHJhaW5fbGRhLCBmaWxlPSIuLi9vdXRwdXQvTERBX3RyYWluLlJEYXRhIikKCiN0cmFpbmluZyBhY2N1cmFjeSBpbiBMREEgbW9kZWwKc291cmNlKCIuLi9saWIvdGVzdF9MREEuUiIpCnByZWRfdHJhaW5fbGRhIDwtIHRlc3RfbGRhKGZpdF90cmFpbl9sZGEsIGRhdF90cmFpbl9wY2EpCmFjY3VfdHJhaW5fbGRhIDwtIG1lYW4ocHJlZF90cmFpbl9sZGEgPT0gZGF0X3RyYWluX3BjYSRlbW90aW9uX2lkeCkKCiNjYXQoIlRoZSB0cmFpbmlnIGFjY3VyYWN5IG9mIG1vZGVsIExEQSIsICJpcyIsIGFjY3VfdHJhaW5fbGRhKjEwMCwgIiUuXG4iKQojY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbCBMREEgPSAiLCB0bV90cmFpbl9sZGFbMV0sICJzIFxuIikKYGBgCgoKIyMjIFN0ZXAgNTogUnVuIHRlc3Qgb24gdGVzdCBpbWFnZXMKCipCYXNlbGluZSBtb2RlbApgYGB7ciB0ZXN0IGdibX0KdG1fdGVzdF9iYXNlbGluZSA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCmlmKHJ1bi50ZXN0KXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5fYmFzZWxpbmUuUkRhdGEiKQogIHRtX3Rlc3RfYmFzZWxpbmUgPC0gc3lzdGVtLnRpbWUoe2xhYmVsX3ByZWRfYmFzZWxpbmUgPC0gYXMuaW50ZWdlcih0ZXN0X2dibShmaXRfdHJhaW5fYmFzZWxpbmUsZmVhdHVyZV90ZXN0LGJlc3Rfbi50cmVlcywgYmVzdF9zaHJpbmthZ2UsIHByZWQudHlwZSA9ICdsaW5rJykpOyAKICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9iX3ByZWRfYmFzZWxpbmUgPC0gdGVzdF9nYm0oZml0X3RyYWluX2Jhc2VsaW5lLCBmZWF0dXJlX3Rlc3QsYmVzdF9uLnRyZWVzLCBiZXN0X3Nocmlua2FnZSwgcHJlZC50eXBlID0gJ3Jlc3BvbnNlJyl9KQp9CmBgYAoKKlNWTSBtb2RlbApgYGB7ciB0ZXN0IHN2bX0KdG1fdGVzdCA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCmlmKHJ1bi50ZXN0LnN2bSl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3N2bS5SRGF0YSIpCiAgdG1fdGVzdF9zdm0gPC0gc3lzdGVtLnRpbWUoewogICAgbGFiZWxfcHJlZF9zdm0gPC0gYXMuaW50ZWdlcihzdm1fdGVzdChmaXRfdHJhaW5fc3ZtLCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdjbGFzcycpKTsgCiAgICBwcm9iX3ByZWRfc3ZtIDwtIHN2bV90ZXN0KGZpdF90cmFpbl9zdm0sIGZlYXR1cmVfdGVzdCwgcHJlZC50eXBlID0gJ3Jlc3BvbnNlJyl9KQp9CmBgYAoKKlhHQm9vc3QKYGBge3IgdGVzdCB4Z2Jvb3N0fQp0bV90ZXN0X3hnYiA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCmlmKHJ1bi50ZXN0KXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5feGdiLlJEYXRhIikKICB0bV90ZXN0X3hnYiA8LSBzeXN0ZW0udGltZSh7bGFiZWxfcHJlZF94Z2IgPC0gcHJlZGljdChmaXRfdHJhaW5feGdiLCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdjbGFzcycpOwogICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsX3ByZWRfeGdiW2xhYmVsX3ByZWRfeGdiID49IDAuNV0gPC0gMTsKICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wcmVkX3hnYltsYWJlbF9wcmVkX3hnYiA8IDAuNV0gPC0gIDA7CiAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYl9wcmVkX3hnYiA8LSBwcmVkaWN0KGZpdF90cmFpbl94Z2IsIGZlYXR1cmVfdGVzdCwgcHJlZC50eXBlID0gJ3Jlc3BvbnNlJyl9KQp9CmBgYAoKKlJhbmRvbSBGb3Jlc3QKYGBge3IgdGVzdCByYW5kb20gZm9yZXN0IH0KdG1fdGVzdF9yZiA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCmlmKHJ1bi50ZXN0KXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5fcmYuUkRhdGEiKQogIHRtX3Rlc3RfcmYgPC0gc3lzdGVtLnRpbWUoe2xhYmVsX3ByZWRfcmYgPC0gcHJlZGljdChmaXRfdHJhaW5fcmFuZG9tZm9yZXN0LCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdjbGFzcycpOwogICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JfcHJlZF9yZiA8LSBwcmVkaWN0KGZpdF90cmFpbl9yYW5kb21mb3Jlc3QsIGZlYXR1cmVfdGVzdCwgdHlwZSA9ICdwcm9iJyl9KQp9IAoKYGBgCgoqTERBCmBgYHtyfQpzb3VyY2UoIi4uL2xpYi90ZXN0X0xEQS5SIikKbG9hZChmaWxlID0gIi4uL291dHB1dC9mZWF0dXJlX3Rlc3RfcGNhLlJEYXRhIikKbG9hZChmaWxlPSIuLi9vdXRwdXQvTERBX3RyYWluLlJEYXRhIikKdG1fdGVzdF9sZGEgPC0gc3lzdGVtLnRpbWUocHJlZF9sZGEgPC0gdGVzdF9sZGEoZml0X3RyYWluX2xkYSwgZGF0X3Rlc3RfcGNhKSkKCmBgYAoKCiMjIyBFdmFsdWF0aW9uCmBgYHtyIGJhc2VsaW5lfQojIyByZXdlaWdodCB0aGUgdGVzdCBkYXRhIHRvIHJlcHJlc2VudCBhIGJhbGFuY2VkIGxhYmVsIGRpc3RyaWJ1dGlvbgpsYWJlbF90ZXN0IDwtIGFzLmludGVnZXIoZGF0X3Rlc3QkbGFiZWwpCndlaWdodF90ZXN0IDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3Rlc3QpKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3Rlc3QpKXsKICB3ZWlnaHRfdGVzdFtsYWJlbF90ZXN0ID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3Rlc3QpIC8gbGVuZ3RoKGxhYmVsX3Rlc3RbbGFiZWxfdGVzdCA9PSB2XSkKfQoKYWNjdV9iYXNlbGluZSA8LSBtZWFuKGxhYmVsX3ByZWRfYmFzZWxpbmUgPT0gbGFiZWxfdGVzdCkKdHByLmZwci5iYXNlbGluZSA8LSBXZWlnaHRlZFJPQyhwcm9iX3ByZWRfYmFzZWxpbmUsIGxhYmVsX3Rlc3QsIHdlaWdodF90ZXN0KQphdWNfYmFzZWxpbmUgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5iYXNlbGluZSkKCgpjYXQoIlRoZSBhY2N1cmFjeSBvZiBtb2RlbCBHQk06IHdpdGggbi50cmVlcz0iLGJlc3Rfbi50cmVlcywiYW5kIHNocmlua2FnZSA9IiwgYmVzdF9zaHJpbmthZ2UsICJpcyIsIGFjY3VfYmFzZWxpbmUqMTAwLCAiJS5cbiIpCmNhdCgiVGhlIEFVQyBvZiBtb2RlbCBHQk06IHdpdGggbi50cmVlcz0iLCBiZXN0X24udHJlZXMsImFuZCBzaHJpbmthZ2UgPSIsIGJlc3Rfc2hyaW5rYWdlLCAiaXMiLCBhdWNfYmFzZWxpbmUsICIuXG4iKQoKCmBgYAoKYGBge3IgZXZhbHV0YXRpb25fU1ZNfQpsYWJlbF90ZXN0IDwtIGFzLmludGVnZXIoZGF0X3Rlc3QkbGFiZWwpCndlaWdodF90ZXN0IDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3Rlc3QpKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3Rlc3QpKXsKICB3ZWlnaHRfdGVzdFtsYWJlbF90ZXN0ID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3Rlc3QpIC8gbGVuZ3RoKGxhYmVsX3Rlc3RbbGFiZWxfdGVzdCA9PSB2XSkKfQoKYWNjdV9zdm0gPC0gbWVhbihsYWJlbF9wcmVkX3N2bSA9PSBsYWJlbF90ZXN0KQp0cHIuZnByLnN2bSA8LSBXZWlnaHRlZFJPQyhwcm9iX3ByZWRfc3ZtLCBsYWJlbF90ZXN0LCB3ZWlnaHRfdGVzdCkKYXVjX3N2bSA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLnN2bSkKY2F0KCJUaGUgYWNjdXJhY3kgb2YgbW9kZWw6IiwgbW9kZWxfbGFiZWxzX3N2bVt3aGljaC5taW4ocmVzX2N2X3N2bSRtZWFuX2Vycm9yKV0sICJpcyIsIGFjY3Vfc3ZtKjEwMCwgIiUuXG4iKQpjYXQoIlRoZSBBVUMgb2YgbW9kZWw6IiwgbW9kZWxfbGFiZWxzX3N2bVt3aGljaC5taW4ocmVzX2N2X3N2bSRtZWFuX2Vycm9yKV0sICJpcyIsIGF1Y19zdm0sICIuXG4iKQoKCmBgYAoKYGBge3IgZXZhbHVhdGlvbl9YR0J9CmxhYmVsX3Rlc3QgPC0gYXMuaW50ZWdlcihkYXRfdGVzdCRsYWJlbCkKbGFiZWxfdGVzdF94Z2IgPC0gbGFiZWxfdGVzdApsYWJlbF90ZXN0X3hnYltsYWJlbF90ZXN0X3hnYj09Ml0gPSAwCmFjY3VfeGdiIDwtIG1lYW4oKGxhYmVsX3ByZWRfeGdiID09IGxhYmVsX3Rlc3RfeGdiKSkKdHByLmZwcl94Z2IgPC0gV2VpZ2h0ZWRST0MocHJvYl9wcmVkX3hnYiwgbGFiZWxfdGVzdF94Z2IpCmF1Y194Z2IgPC0gV2VpZ2h0ZWRBVUModHByLmZwcl94Z2IpCgoKY2F0KCJUaGUgYWNjdXJhY3kgb2YgdGhlIFhHQm9vc3QgbW9kZWw6IiwgImlzIiwgYWNjdV94Z2IqMTAwLCAiJS5cbiIpCmNhdCgiVGhlIEFVQyBvZiB0aGUgWEdCb29zdCBtb2RlbDoiLCAiaXMiLCBhdWNfeGdiLCAiLlxuIikKCmBgYAoKYGBge3IgZXZhbHVhdGlvbl9yZn0KbGFiZWxfdGVzdCA8LSBhcy5pbnRlZ2VyKGRhdF90ZXN0JGxhYmVsKQphY2N1X3JmIDwtIG1lYW4obGFiZWxfcHJlZF9yZiA9PSBsYWJlbF90ZXN0KQp0cHIuZnByLnJmIDwtIFdlaWdodGVkUk9DKHByb2JfcHJlZF9yZlssMl0sIGxhYmVsX3Rlc3QpCmF1Y19yZiA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLnJmKQoKCmNhdCgiVGhlIGFjY3VyYWN5IG9mIHRoZSBSYW5kb20gRm9yZXN0IG1vZGVsOiIsICJpcyIsIGFjY3VfcmYqMTAwLCAiJS5cbiIpCmNhdCgiVGhlIEFVQyBvZiB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbDoiLCAiaXMiLCBhdWNfcmYsICIuXG4iKQpgYGAKCmBgYHtyIGV2YWx1YXRpb25fUENBK0xEQX0KYWNjdV9sZGEgPC0gbWVhbihkYXRfdGVzdF9wY2EkZW1vdGlvbl9pZHggPT0gcHJlZF9sZGEpCmxhYmVsX2xkYSA8LSBhcy5udW1lcmljKGRhdF90ZXN0X3BjYSRlbW90aW9uX2lkeCkKdHByLmZwci5sZGEgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhwcmVkX2xkYSksIGxhYmVsX2xkYSkKYXVjX2xkYSA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmxkYSkKCmNhdCgiVGhlIGFjY3VyYWN5IG9mIHRoZSBQQ0ErTERBIG1vZGVsOiIsICJpcyIsIGFjY3VfbGRhKjEwMCwgIiUuXG4iKQpjYXQoIlRoZSBBVUMgb2YgdGhlIFBDQStMREEgbW9kZWw6IiwgImlzIiwgYXVjX2xkYSwgIi5cbiIpCmBgYAoKIyMjIFN1bW1hcml6ZSBSdW5uaW5nIFRpbWUKUHJlZGljdGlvbiBwZXJmb3JtYW5jZSBtYXR0ZXJzLCBzbyBkb2VzIHRoZSBydW5uaW5nIHRpbWVzIGZvciBjb25zdHJ1Y3RpbmcgZmVhdHVyZXMgYW5kIGZvciB0cmFpbmluZyB0aGUgbW9kZWwsIGVzcGVjaWFsbHkgd2hlbiB0aGUgY29tcHV0YXRpb24gcmVzb3VyY2UgaXMgbGltaXRlZC4gCmBgYHtyIHJ1bm5pbmdfdGltZV9iYXNlbGluZX0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHRtX3RyYWluX2Jhc2VsaW5lWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF9iYXNlbGluZVsxXSwgInMgXG4iKQpgYGAKCgpgYGB7ciBydW5uaW5nX3RpbWVfU1ZNfQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90ZXN0WzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgbW9kZWw9IiwgdG1fdHJhaW5fc3ZtWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF9zdm1bMV0sICJzIFxuIikKYGBgCgoKYGBge3IgcnVubmluZ190aW1lX1hHQn0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHhnYl90cmFpbl90aW1lWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF94Z2JbMV0sICJzIFxuIikKCmBgYAoKYGBge3IgcnVubmluZ190aW1lX3JmfQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90ZXN0WzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgcmFuZG9tIGZvcmVzdCBtb2RlbD0iLCByZl90cmFpbl90aW1lWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdGVzdGluZyByYW5kb20gZm9yZXN0IG1vZGVsPSIsIHRtX3Rlc3RfcmZbMV0sICJzIFxuIikKYGBgCgoKYGBge3IgcnVubmluZ190aW1lX1BDQStMREF9CmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRyYWluaW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdHJhaW5bMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdGVzdGluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgUENBID0iLCB0bV90cmFpbl9wY2FbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIFBDQSA9IiwgdG1fdGVzdF9wY2FbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbCBMREEgPSAiLCB0bV90cmFpbl9sZGFbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsIExEQSA9ICIsdG1fdGVzdF9sZGFbMV0sICJzIFxuIikKYGBgCiMjI1JlZmVyZW5jZQotIER1LCBTLiwgVGFvLCBZLiwgJiBNYXJ0aW5leiwgQS4gTS4gKDIwMTQpLiBDb21wb3VuZCBmYWNpYWwgZXhwcmVzc2lvbnMgb2YgZW1vdGlvbi4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDExMSgxNSksIEUxNDU0LUUxNDYyLgoKCgoKCgoKCgoKCgoK